package com.zw.bigqueue.page;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sun.misc.Unsafe;

import java.io.Closeable;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;

/**
 * @author create by zhouzongbo on 2020/7/8.
 */
public class MappedPageImpl implements IMappedPage, Closeable {
    private static final Logger log = LoggerFactory.getLogger(MappedPageImpl.class);

    /**
     * 本地线程缓冲区
     */
    private ThreadLocalByteBuffer threadLocalByteBuffer;

    /**
     * 数据是否存在变更, 如果有变更，并且调用flush()方法，就会把数据存入到磁盘中
     */
    private volatile boolean dirty = false;

    /**
     * true: 关闭; false: 未关闭
     */
    private volatile boolean close = false;

    /**
     * 文件名
     */
    private final String pageFile;

    /**
     * 下标
     */
    private final long index;

    public MappedPageImpl(MappedByteBuffer byteBuffer, String pageFile, long index) {
        this.pageFile = pageFile;
        this.index = index;
        this.threadLocalByteBuffer = new ThreadLocalByteBuffer(byteBuffer);
    }

    @Override
    public ByteBuffer getLocal(int position) {
        final ByteBuffer byteBuffer = this.threadLocalByteBuffer.get();
        byteBuffer.position(position);
        return byteBuffer;
    }

    @Override
    public byte[] getLocal(int position, int length) {
        final ByteBuffer buf = this.getLocal(position);
        byte[] data = new byte[length];
        buf.get(data);
        return data;
    }

    @Override
    public boolean isClose() {
        return this.close;
    }

    @Override
    public void setDirty(boolean dirty) {
        this.dirty = dirty;
    }

    @Override
    public String getPageFile() {
        return pageFile;
    }

    @Override
    public long getPageIndex() {
        return this.index;
    }

    @Override
    public void flush() {
        // 把内存中变更的数据存入到磁盘中
        synchronized (this) {
            if (close) {
                return;
            }

            if (dirty) {
                final MappedByteBuffer srcBuf = (MappedByteBuffer) this.threadLocalByteBuffer.getSrc();
                srcBuf.force();
                dirty = false;
                if (log.isDebugEnabled()) {
                    log.debug("Mapped page for " + this.pageFile + "was just flushed.");
                }
            }
        }
    }

    @Override
    public void close() throws IOException {
        // 释放流信息
        synchronized (this) {
            // 如果已经关闭, 返回
            if (close) {
                return;
            }
            // 关闭前判断是否有更新内容
            flush();

            final MappedByteBuffer srcBuf = (MappedByteBuffer) this.threadLocalByteBuffer.getSrc();
            // 清除缓冲区
            unmap(srcBuf);
            // hint GC
            this.threadLocalByteBuffer = null;
            close = true;
            if (log.isDebugEnabled()) {
                log.debug("Mapped page " + this.pageFile + "was just unmap and closed");
            }
        }
    }

    @Override
    public String toString() {
        return "MappedPageImpl{" + "pageFileName='" + pageFile + '\'' + ", index=" + index + '}';
    }

    /**
     * 清除缓冲区数据
     * @param buf buf
     */
    public static void unmap(ByteBuffer buf) {
        Cleaner.clean(buf);
    }

    /**
     * // java.nio.DirectByteBuffer
     * // sun.misc.Cleaner
     * 直接清除缓冲区Helper
     */
    public static class Cleaner {
        /**
         * 是否支持情况缓冲区数据
         */
        public static final boolean CLEAN_SUPPORT;
        /**
         * 获取Cleaner方法
         */
        private static final Method DIRECT_BUFFER_CLEANER;
        private static final Method DIRECT_BUFFER_CLEANER_CLEAN;

        static {
            // 初始化清除缓冲区的方法
            Method cleanerX = null;
            Method cleanerClean = null;
            boolean supportCleaner = false;
            try {
                // JDK14 可以替换为 sun.misc.Unsafe
                cleanerX = Class.forName("java.nio.DirectByteBuffer").getMethod("cleaner");
                cleanerX.setAccessible(Boolean.TRUE);
                cleanerClean = Class.forName("sun.misc.Cleaner").getMethod("clean");
                cleanerClean.setAccessible(Boolean.TRUE);
                supportCleaner = true;
            } catch (Exception e) {
                // quiet exception
            }

            CLEAN_SUPPORT = supportCleaner;
            DIRECT_BUFFER_CLEANER = cleanerX;
            DIRECT_BUFFER_CLEANER_CLEAN = cleanerClean;
        }

        /**
         * 清除缓冲区数据, 只有直接缓冲区才允许被清除
         * @param byteBuffer buf
         */
        public static void clean(ByteBuffer byteBuffer) {
            if (byteBuffer ==  null) {
                return;
            }
            // 当前的缓冲区是直接缓冲区才允许被clean
            if (CLEAN_SUPPORT && byteBuffer.isDirect()) {
                try {
                    final Object cleaner = DIRECT_BUFFER_CLEANER.invoke(byteBuffer);
                    DIRECT_BUFFER_CLEANER_CLEAN.invoke(cleaner);
                } catch (Exception e) {
                    // quiet exception
                }
            }
        }
    }


    /**
     * 本地线程中的字节缓冲区数据
     */
    public static class ThreadLocalByteBuffer extends ThreadLocal<ByteBuffer> {
        /**
         * 源: 字节缓冲区
         */
        private final ByteBuffer src;

        public ThreadLocalByteBuffer(ByteBuffer src) {
            this.src = src;
        }

        public ByteBuffer getSrc() {
            return src;
        }

        /**
         * 该方法当前线程局部变量的“初始值”.
         * @return ByteBuffer
         */
        @Override
        protected synchronized ByteBuffer initialValue() {
            return src.duplicate();
        }
    }
}
